﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Disposables;
using System.Linq;
using System.Linq.Functional;
using System.Windows;
using System.Windows.Controls;
using SoftSource.Common;
using SoftSource.MVVMC;
using SoftSource.Reactive;
using SoftSource.Timecard.Resources;
using SoftSource.Timecard.ViewModelInterfaces;
using SoftSource.Timecard.Web.Data;
using SoftSource.Timecard.Web.Resources;

namespace SoftSource.Timecard.Views
{
    public sealed partial class EntryManagementView : UserControl, IView<IEntryManagementViewModelConsume>, IDisposable
    {
        #region Fields
        private Filter<ProjectSummary> projectFilter;
        private Filter<UserSummary> userFilter;
        private Filter<EntryLog> entryFilter;
        private Predicate<ProjectSummary> projectCheckBoxFilter;
        private Predicate<UserSummary> userCheckBoxFilter;
        private CompositeDisposable compositeDisposable;
        private bool includeBilled;
        #endregion Fields

        public EntryManagementView()
        {
            InitializeComponent();

            this.compositeDisposable = new CompositeDisposable();

            Loaded += new RoutedEventHandler(EntryManagementView_Loaded);

            this.EntryDataGrid.RowEditEnded += (sender, e) => this.SummarizeEntries();
            this.EntryDataGrid.SizeChanged += new SizeChangedEventHandler(EntryDataGrid_SizeChanged);
            this.EntryDataGrid.LoadingRow += new EventHandler<DataGridRowEventArgs>(EntryDataGrid_LoadingRow);
        }

        #region IView<IEntryManagementViewModelConsume> Members

        public IEntryManagementViewModelConsume ViewModel
        {
            get { return this.DataContext as IEntryManagementViewModelConsume; }
            set { this.DataContext = value; }
        }

        #endregion

        #region Filters
        private void RunFilters()
        {
            if (this.ViewModel != null)
            {
                if (this.projectFilter != null)
                    this.projectFilter.Refresh.TryInvoke();

                if (this.userFilter != null)
                    this.userFilter.Refresh.TryInvoke();

                if (this.entryFilter != null)
                    this.entryFilter.Refresh.TryInvoke();

                this.SummarizeEntries();
            }
        }

        private void AttatchFilters()
        {
            this.userCheckBoxFilter = summary =>
                {
                    if (summary.IsActive)
                        return true; // Always include active

                    if (!this.UserActiveCheckBox.IsChecked ?? false)
                        return true; // If the User is not active, but the checkbox is unchecked, then include it

                    return false; // if the User doesn't match either of the above criteria, then don't include it
                };

            this.projectCheckBoxFilter = summary =>
                {
                    int status = summary.StatusID;
                    bool include = false;

                    switch (status)
                    {
                        case ProjectStatus.Active:
                            if (this.ProjectActiveCheckBox.IsChecked ?? false)
                                include = true;
                            break;
                        case ProjectStatus.Completed:
                            if (this.CompletedCheckBox.IsChecked ?? false)
                                include = true;
                            break;
                        case ProjectStatus.Suspended:
                            if (this.SuspendedCheckBox.IsChecked ?? false)
                                include = true;
                            break;
                        case ProjectStatus.Delayed:
                            if (this.DelayedCheckBox.IsChecked ?? false)
                                include = true;
                            break;
                        case ProjectStatus.NotStarted:
                            if (this.NotStartedCheckBox.IsChecked ?? false)
                                include = true;
                            break;
                        default:
                            include = false;
                            break;
                    }

                    return include;
                };

            this.projectFilter.Add(summary => this.projectCheckBoxFilter(summary));
            this.userFilter.Add(summary => this.userCheckBoxFilter(summary));
        }

        private void CheckBoxFilter_CheckChanged(object sender, RoutedEventArgs e)
        {
            this.RunFilters();
        }
        #endregion Filters

        #region IDisposable Members

        public void Dispose()
        {
            this.compositeDisposable.Dispose();
        }

        #endregion

        #region Events
        void EntryManagementView_Loaded(object sender, RoutedEventArgs e)
        {
            if (DesignerProperties.IsInDesignTool)
                return;

            if (this.ViewModel != null)
            {
                this.ViewModel.Entries
                    .Nullable()
                    .Where(value => value != null)
                    .Subscribe(value =>
                    {
                        this.entryFilter = value.AttachFilter<EntryLog>();

                        this.entryFilter.Add(entry =>
                        {
                            if (this.includeBilled)
                                return true; // View all entries

                            if (entry.BillingStateID == BillingState.Billed)
                                return false; // Do not view billed entries

                            return true; // View everything else
                        });

                        this.SummarizeEntries();
                    });

                var whenEntriesCollectionChanged = Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>(
                    handler => (sender2, e2) => handler(sender2, e2),
                    handler => this.ViewModel.Entries.Value.CollectionChanged += handler,
                    handler => this.ViewModel.Entries.Value.CollectionChanged -= handler);

                this.Busy.BusyContent = ApplicationStrings.LoadingMessage;
                this.Busy.IsBusy = true;

                var loadDisposable = this.ViewModel.Load // Load the data required to use this ViewModel
                    .Nullable()
                    .Do(unit =>
                        {
                            this.Busy.IsBusy = false;

                            this.projectFilter = this.ViewModel.Projects.AttachFilter<ProjectSummary>();
                            this.userFilter = this.ViewModel.Users.AttachFilter<UserSummary>();

                            this.AttatchFilters();

                            this.RunFilters();
                        })
                    .SelectMany(this.ViewModel.Entries)
                    .Where(pcv => pcv != null)
                    .ToUnit()
                    .Do(unit => this.ViewModel.Entries.Value
                        .Cast<EntryLog>()
                        .Run(entry => this.SubscribeToEntry(entry)))
                    .Do(unit => this.compositeDisposable.Add(whenEntriesCollectionChanged
                        .Select(evnt => evnt.EventArgs)
                        .Where(args => args.Action == NotifyCollectionChangedAction.Add)
                        .Do(args => args.NewItems
                            .Cast<EntryLog>()
                            .Run(this.SubscribeToEntry))
                        .Subscribe()))
                    .SubscribeOnDispatcher()
                    .Subscribe();
            }
        }

        private void SubscribeToEntry(EntryLog entry)
        {
            this.compositeDisposable.Add(entry.WhenPropertyChanged()
                .Where(evnt => evnt.EventArgs.PropertyName == ObjectReflection.GetPropertyName(() => entry.ProjectID))
                .Do(evnt => entry.CategoryID = App.Model.ProjectSummaries.Entities
                    .Single(p => p.ProjectID == entry.ProjectID).ProjectCategorySummaries
                    .OrderBy(pc => pc.CategorySummary.Name)
                    .First().CategoryID)
                .Subscribe());
        }

        void EntryDataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
        {
            DataGrid entryDataGrid = (DataGrid)sender;
            // Do not change column size if Visibility State Changed 
            if (entryDataGrid.RenderSize.Width != 0)
            {
                double allColumnsSizes = 0.0;
                foreach (DataGridColumn column in entryDataGrid.Columns)
                {
                    allColumnsSizes += column.ActualWidth;
                }
                // Space available to fill ( -18 Standard vScrollbar) 
                double spaceAvailable = (entryDataGrid.RenderSize.Width - 18) - allColumnsSizes;

                var col = entryDataGrid.Columns.SingleOrDefault(c => (c.Header is string) && c.Header.ToString() == "Reason");

                if (spaceAvailable > 0)
                    col.Width = new DataGridLength(col.ActualWidth + spaceAvailable);
            }
        }

        void EntryDataGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            DataGrid entryDataGrid = (DataGrid)sender;
            // Do not change column size if Visibility State Changed 
            if (entryDataGrid.RenderSize.Width != 0)
            {
                double allColumnsSizes = 0.0;
                foreach (DataGridColumn column in entryDataGrid.Columns)
                {
                    allColumnsSizes += column.ActualWidth;
                }

                // Space available to fill ( -18 Standard vScrollbar) 
                double spaceAvailable = (entryDataGrid.RenderSize.Width - 18) - allColumnsSizes;

                var col = entryDataGrid.Columns.SingleOrDefault(c => (c.Header is string) && c.Header.ToString() == "Reason");

                if (spaceAvailable > 0)
                    col.Width = new DataGridLength(col.ActualWidth + spaceAvailable);
            }

        }

        private void Refresh_Click(object sender, RoutedEventArgs e)
        {            
            int? projectID = null;
            int? userID = null;
            DateTime? startDate = null;
            DateTime? endDate = null;

            if (this.StartDateExpander.IsExpanded &&
                this.StartDatePicker.SelectedDate != null &&
                this.StartDatePicker.IsEnabled)
            {
                startDate = this.StartDatePicker.SelectedDate;
            }

            if (this.EndDateExpander.IsExpanded &&
                this.EndDatePicker.SelectedDate != null &&
                this.EndDatePicker.IsEnabled)
            {
                endDate = this.EndDatePicker.SelectedDate;
            }

            if (this.ProjectExpander.IsExpanded &&
                this.ProjectAutoCompleteBox.SelectedItem != null &&
                this.ProjectAutoCompleteBox.IsEnabled &&
                this.ProjectAutoCompleteBox.SelectedItem is ProjectSummary)
            {
                ProjectSummary summary = (ProjectSummary)this.ProjectAutoCompleteBox.SelectedItem;
                projectID = summary.ProjectID;
            }

            if (this.UserExpander.IsExpanded &&
                this.UserAutoCompleteBox.SelectedItem != null &&
                this.UserAutoCompleteBox.IsEnabled &&
                this.UserAutoCompleteBox.SelectedItem is UserSummary)
            {
                UserSummary summary = (UserSummary)this.UserAutoCompleteBox.SelectedItem;
                userID = summary.UserID;
            }

            var load = this.ViewModel.Refresh.TryInvoke(
                projectID,
                userID,
                startDate,
                endDate);

            this.Busy.BusyContent = ApplicationStrings.LoadingMessage;
            this.Busy.IsBusy = true;

            load.Subscribe(unit => this.Busy.IsBusy = false);
        }

        private void AddWeek_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this.StartDatePicker.SelectedDate != null)
            {
                this.StartDatePicker.SelectedDate = this.StartDatePicker.SelectedDate.Value.AddDays(7);
            }

            if (this.EndDatePicker.SelectedDate != null)
            {
                this.EndDatePicker.SelectedDate = this.EndDatePicker.SelectedDate.Value.AddDays(7);
            }
        }

        private void SubtractWeek_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this.StartDatePicker.SelectedDate != null)
            {
                this.StartDatePicker.SelectedDate = this.StartDatePicker.SelectedDate.Value.AddDays(-7);
            }

            if (this.EndDatePicker.SelectedDate != null)
            {
                this.EndDatePicker.SelectedDate = this.EndDatePicker.SelectedDate.Value.AddDays(-7);
            }
        }

        private void NoProject_CheckChanged(object sender, System.Windows.RoutedEventArgs e)
        {
            if (sender != null && sender is Expander && this.ProjectAutoCompleteBox != null)
            {
                Expander noProject = (Expander)sender;
                bool enable = noProject.IsExpanded;

                this.ProjectAutoCompleteBox.IsEnabled = enable;
                this.ProjectActiveCheckBox.IsEnabled = enable;
                this.CompletedCheckBox.IsEnabled = enable;
                this.SuspendedCheckBox.IsEnabled = enable;
                this.DelayedCheckBox.IsEnabled = enable;
                this.NotStartedCheckBox.IsEnabled = enable;
            }
        }

        private void NoUser_CheckChanged(object sender, System.Windows.RoutedEventArgs e)
        {
            if (sender != null && sender is Expander && this.UserAutoCompleteBox != null)
            {
                Expander noUser = (Expander)sender;
                bool enable = noUser.IsExpanded;

                this.UserAutoCompleteBox.IsEnabled = enable;
                this.UserActiveCheckBox.IsEnabled = enable;
            }
        }

        private void BilledEntries_CheckChanged(object sender, RoutedEventArgs e)
        {
            if (sender != null && sender is CheckBox)
            {
                CheckBox checkBox = (CheckBox)sender;
                this.includeBilled = checkBox.IsChecked ?? false;
                this.RunFilters();
            }
        }

        private void SaveEntries_Click(object sender, RoutedEventArgs e)
        {
            this.Busy.BusyContent = "Saving...";
            this.Busy.IsBusy = true;

            this.ViewModel.SaveApprovals.Subscribe(unit => this.Busy.IsBusy = false);
        }

        private void ApproveAllEntries_CheckChanged(object sender, RoutedEventArgs e)
        {
            if (this.ViewModel != null && this.ViewModel.Entries != null && sender is CheckBox)
            {
                CheckBox checkBox = (CheckBox)sender;
                int state = BillingState.NotApproved;
                if (checkBox.IsChecked ?? false)
                {
                    state = BillingState.Approved;
                }

                var entries = this.ViewModel.Entries.Value.Cast<EntryLog>().Where(entry => entry.BillingStateID != BillingState.Billed);

                foreach (var entry in entries)
                {
                    entry.BillingStateID = state;
                }
            }
        }

        private void EntryDataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
        {
            var entry = e.Row.DataContext as EntryLog;

            bool edit = false;

            if (WebContext.Current.User.IsInRole(AuthenticationDataResources.AdministratorRole))
            {
                edit = true;
            }
            else if (WebContext.Current.User.IsInRole(AuthenticationDataResources.ProjectManagerRole))
            {
                if (e.Column.Header != null)
                    edit = e.Column.Header.ToString()
                        .Match() // Pattern matching from System.Linq.Functional                        
                        .With(val => val == "Reason", true)
                        .With(val => val == "Review By Admin", true)
                        .Else(val => false)
                        .Do();
            }

            if (entry != null && entry.BillingStateID != BillingState.Billed && edit == true)
            {
                e.Cancel = false;
            }
            else
            {
                e.Cancel = true;
            }
        }

        private void EntryDataGrid_RowEditEnded(object sender, DataGridRowEditEndedEventArgs e)
        {
            this.ViewModel.SaveApprovals.Subscribe();
        }
        #endregion Events

        #region Methods
        private void OpenEntryAssociationEditor(EntryLog entry)
        {
            if (this.ViewModel.EditEntryAssociations != null)
            {
                ChildWindow cw = new ChildWindow();
                cw.Title = "Edit Associations";

                var ui = this.ViewModel.EditEntryAssociations.TryInvoke(entry, cw.Close);
                cw.Content = ui;

                cw.Show();
            }
        }

        private void SummarizeEntries()
        {
            if (this.ViewModel != null && this.ViewModel.Entries != null && this.ViewModel.Entries.Value != null)
            {
                var entries = this.ViewModel.Entries.Value.Cast<EntryLog>();
                IEnumerable<EntryLog> summedEntries = entries;
                if (!this.includeBilled)
                    summedEntries = entries.Where(entry => entry.BillingStateID != BillingState.Billed);

                this.SumEntries(summedEntries);
            }
        }

        private void SumEntries(IEnumerable<EntryLog> summedEntries)
        {
            if (summedEntries == null)
                return;

            this.ActualHoursTotal.Text = summedEntries.Select(e => e.Duration).Sum().ToString();
            this.BillableHoursTotal.Text = summedEntries.Select(e => e.BillableHours).Sum().ToString();
        }
        #endregion Methods
    }
}